package com.springboot.frame.oauth.server.config;

import com.springboot.frame.oauth.server.security.user.SecurityUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;

import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;

/**
 * @author liheng
 * @ClassName OAuth2ServerConfig
 * @Description
 * @date 2020-04-24 16:51
 */
@Configuration
public class OAuth2ServerConfig {
    /**
     * 认证授权服务
     */
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
        @Autowired
        private AuthenticationManager authenticationManager;
        @Qualifier("dataSource")
        @Autowired
        private DataSource dataSource;
        @Autowired
        private AuthorizationCodeServices authorizationCodeServices;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Autowired
        private SecurityUserService userDetailsService;


        /**
         * ClientDetails实现
         * 基于jdbc
         *
         * @return
         */
        @Bean
        public ClientDetailsService clientDetails() {
            return new JdbcClientDetailsService(dataSource);
        }

        /**
         * token生命周期管理---基于jdbc
         *
         * @return
         */
        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }

        /**
         * token生命周期管理---基于redis
         *
         * @Autowired private RedisConnectionFactory redisConnectionFactory;
         * @Bean public TokenStore tokenStore() {
         * <p>
         * return new RedisTokenStore(redisConnectionFactory);
         * <p>
         * }
         */
        @Bean
        public ApprovalStore approvalStore() {
            return new JdbcApprovalStore(dataSource);
        }

        @Bean
        public AuthorizationCodeServices authorizationCodeServices() {
            // 加入对授权码模式的支持
            return new JdbcAuthorizationCodeServices(dataSource);
        }

        @Bean
        @Primary
        public DefaultTokenServices defaultTokenServices() {
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setTokenStore(tokenStore());
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setClientDetailsService(clientDetails());
            // token有效期自定义设置，默认12小时
            tokenServices.setAccessTokenValiditySeconds(60 * 30);
            //默认30天，这里修改
            tokenServices.setRefreshTokenValiditySeconds(60 * 60);
            // 以下内容在数据库中有值
            return tokenServices;
        }


        /**
         * 复写授权客户端
         *
         * @param clients
         * @throws Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 1. 数据库的方式
            // 设置客户端的配置从数据库中读取，存储在oauth_client_details表
            clients.withClientDetails(clientDetails());
        }


        /**
         * 声明授权和token的端点以及token的服务的一些配置信息，
         * 比如采用什么存储方式、token的有效期等
         *
         * @param endpoints
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                    .pathMapping("/oauth/token", "/oauth2.0/token") // 更改请求端点映射
                    .pathMapping("/oauth/authorize", "/oauth2.0/authorize") // 更改请求端点映射
                    // 自定义授权页面
                    .pathMapping("/oauth/confirm_access", "/confirm_access")
                    .approvalStore(approvalStore())
                    .tokenServices(defaultTokenServices())  // 加载token配置
                    .authenticationManager(authenticationManager) // 开启密码验证，来源于 WebSecurityConfigurerAdapter
                    .userDetailsService(userDetailsService) // 配置自定义的用户权限数据，不配置会导致token无法刷新
                    .authorizationCodeServices(authorizationCodeServices) // 读取验证用户的信息
                    .setClientDetailsService(clientDetails());

        }

        /**
         * 声明安全约束，哪些允许访问，哪些不允许访问
         *
         * @param oauthServer
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            //允许表单认证
            oauthServer.allowFormAuthenticationForClients();//支持把secret和clientid写在url上，否则需要在头上
            oauthServer.passwordEncoder(passwordEncoder);
            // 对于CheckEndpoint控制器[框架自带的校验]的/oauth/check端点允许所有客户端发送器请求而不会被Spring-security拦截
            oauthServer.tokenKeyAccess("permitAll()")  // 获取 token 的策略
                    .checkTokenAccess("isAuthenticated()");
        }
    }

    /**
     * 资源认证服务
     */
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            // 如果关闭 stateless，则 accessToken 使用时的 session id 会被记录，后续请求不携带 accessToken 也可以正常响应
            // 如果 stateless 为 true 打开状态，则 每次请求都必须携带 accessToken 请求才行，否则将无法访问
            resources.stateless(true);
        }

        /**
         * oauth2 的资源拦截处理
         * 为oauth2单独创建角色，这些角色只具有访问受限资源的权限，可解决token失效的问题
         *
         * @param http
         * @throws Exception
         */
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .antMatchers("/all")
                    .permitAll()
                    .antMatchers("/userinfo")
                    .authenticated()
                    //.withObjectPostProcessor(urlObjectPostProcessor())
                    .and()
                    .formLogin()
                    // 前后端分离的情况下可以不指定
                    .loginPage("/login")
                    // 这里必须要写formLogin()，
                    // 不然原有的UsernamePasswordAuthenticationFilter不会出现，
                    // 可以通过 loginProcessingUrl 方法来指定登录接口地址
                    .loginProcessingUrl("/login")
                    .permitAll()
                    // 登录失败的拦截
                    //.failureHandler(securityAuthenticationFailureHandler)
                    //.failureForwardUrl("/failure")
                    // 登录成功的拦截
                    //.successForwardUrl("/userinfo")
                    // 不进行登录成功拦截可自行跳转到授权页
                    // .successHandler(securityAuthenticationSuccessHandler)
                    //.defaultSuccessUrl("/index")
                    .and()
                    .exceptionHandling()
                    // 此时是认证失败
                    .authenticationEntryPoint((request, response, exception) -> {
                        // oauth2 认证失败导致的，还有一种可能是非oauth2认证失败导致的，比如没有传递token，但是访问受权限保护的方法
                        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                        response.setContentType(MediaType.APPLICATION_JSON.toString());
                        response.getWriter().write("{\"code\":-3,\"message\":\"认证失败\"}");
                    })
                    .accessDeniedHandler((request, response, exception) -> {
                        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                        response.setContentType(MediaType.APPLICATION_JSON.toString());
                        response.getWriter().write("{\"code\":-4,\"message\":\"您无权限访问\"}");
                    })
                    // 未授权用户访问无权限时的处理
                    //.authenticationEntryPoint(securityAuthenticationEntryPoint)
                    // 认证过的用户访问没有权限资源的处理
                    //.accessDeniedHandler(securityAccessDeniedHandler)
                    .and()
                    .logout()
                    .deleteCookies("JSESSIONID")
                    // 注销 URL
                    .logoutUrl("/logout")
                    // 注销成功拦截
                    //.logoutSuccessHandler(securityLogoutSuccessHandler)
                    .permitAll();
            http
                    // 获取登录用户的 session
                    // 设置session是无状态的
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    // 无效session跳转
                    .invalidSessionUrl("/login")
                    // 同账号最大允许登录数
                    .maximumSessions(1)
                    //session过期跳转
                    .expiredUrl("/login");

        }
    }
}
